package io.github.yotta.export.core.export.impl;

import io.github.yotta.common.api.model.ApiPageRequest;
import io.github.yotta.export.core.enums.DataExportStatus;
import io.github.yotta.export.core.export.ExcelDataExporter;
import io.github.yotta.export.core.export.ExcelExporterExecutor;
import io.github.yotta.export.core.model.ExecuteContext;
import io.github.yotta.export.core.model.XlsxDataExportResult;
import io.github.yotta.export.core.provider.FileProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Consumer;

/**
 * @author yotta
 * @date 2023/10/30 8:49
 */
@Slf4j
public abstract class AbstractExporterExecutor implements ExcelExporterExecutor {
    private final File tempDir;

    private final ExecutorService executorService;

    private final FileProvider fileProvider;

    public AbstractExporterExecutor(ExecutorService executorService, FileProvider fileProvider) throws IOException {
        this.tempDir = Files.createTempDirectory("yotta-data-export").toFile();
        this.executorService = executorService;
        this.fileProvider = fileProvider;
        this.registerShutdownHook();
    }


    @Override
    public <R extends ApiPageRequest, M> XlsxDataExportResult exportXlsx(R exportParam,
                                                                         ExcelDataExporter<M, R> dataExporter) {
        return exportXlsx(exportParam, dataExporter, null);
    }


    protected abstract <R extends ApiPageRequest, M> Long doExport(R exportParam,
                                                                   ExcelDataExporter<M, R> dataExporter,
                                                                   ExecuteContext dataExecuteContext,
                                                                   OutputStream outputStream) throws IOException;


    @Override
    public <R extends ApiPageRequest, M> Future<XlsxDataExportResult> exportXlsxAsync(R exportParam,
                                                                                      ExcelDataExporter<M, R> dataExporter,
                                                                                      Consumer<XlsxDataExportResult> onComplete) {
        Objects.requireNonNull(executorService);
        return executorService.submit(() -> this.exportXlsx(exportParam, dataExporter, onComplete));
    }


    protected <R extends ApiPageRequest, M> XlsxDataExportResult exportXlsx(R exportParam,
                                                                            ExcelDataExporter<M, R> dataExporter, Consumer<XlsxDataExportResult> onComplete) {
        XlsxDataExportResult exportResult = new XlsxDataExportResult();
        try {
            export(exportResult, exportParam, dataExporter, onComplete);
        } catch (Throwable e) {
            log.error("export xlsx data error by {}", e.getMessage(), e);
            exportResult.setStatus(DataExportStatus.ERROR.getCode());
            exportResult.setMessage(e.getMessage());
            dataExporter.exportError(exportParam, exportResult, e);
            if (onComplete != null) {
                onComplete.accept(exportResult);
            }
        }
        return exportResult;

    }


    private <R extends ApiPageRequest, M> void export(XlsxDataExportResult exportResult,
                                                      R exportParam,
                                                      ExcelDataExporter<M, R> dataExporter,
                                                      Consumer<XlsxDataExportResult> onComplete) throws IOException, InstantiationException, IllegalAccessException {

        ExecuteContext dataExecuteContext = new ExecuteContext();
        dataExporter.exportStarted(exportParam, dataExecuteContext);
        String exporterFileName = dataExporter.exportFileName(exportParam, dataExecuteContext);
        String exporterFilePath = this.fileProvider.generateFilePath(exporterFileName,
                dataExporter.excelType(exportParam, dataExecuteContext).getExt());
        File tempFile = createTempFile(exporterFileName);
        try (OutputStream outputStream = Files.newOutputStream(tempFile.toPath())) {
            Long exportedCount = doExport(exportParam, dataExporter, dataExecuteContext, outputStream);
            fileProvider.saveFile(exporterFilePath, tempFile);
            exportResult.setExportFileName(exporterFileName + dataExporter.excelType(exportParam, dataExecuteContext).getExt());
            exportResult.setExportedCount(exportedCount);
            exportResult.setStatus(DataExportStatus.SUCCESS.getCode());
            exportResult.setExportFilePath(exporterFilePath);
            exportResult.setExportFileUrl(fileProvider.getFileUrl(exporterFilePath));
            dataExporter.exportFinished(exportResult, dataExecuteContext);
        }
        if (onComplete != null) {
            onComplete.accept(exportResult);
        }
        tempFile.deleteOnExit();
    }

    private File createTempFile(String exporterFileName) throws IOException {
        String tempFileName = UUID.randomUUID() + "__" + exporterFileName + ".";
        String absFilePath = Paths.get(tempDir.getPath(), tempFileName).toString();
        File directory = new File(Paths.get(absFilePath).getParent().toString());
        if (!directory.exists()) {
            boolean d = directory.mkdirs();
            if (!d) {
                throw new IOException(MessageFormat.format(
                        "Failed to create folder, {0}", directory));
            }
        }
        return new File(absFilePath);
    }

    private void registerShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                FileUtils.deleteDirectory(tempDir);
            } catch (IOException e) {
                // logger may have already shutdown
                e.printStackTrace();
            }
        }));
    }
}
